﻿// (c) Norbert Huffschmid
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Threading;
using NProtocol;
using NProtocol.Codec;
using ShoutBox.Core.Resources;

namespace ShoutBox.Core
{
    /// <summary>
    /// ShoutBox listener state
    /// </summary>
    public enum ListenerState
    {
        Idle = 0,    // listener is running, but no remote session members are available
        Waiting = 1, // remote session members exist, but currently keep silent
        Active = 2   // at least one session member is currently shouting
    }


    /// <summary>
    /// Base class of all ShoutBox view models
    /// </summary>
    public abstract class ViewModel : INotifyPropertyChanged, IDisposable
    {
        /// <summary>
        /// Notifies listeners that a property value has changed.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;


        protected Dispatcher uiDispatcher;

        private ObservableCollection<ISessionMember> sessionMembers = new ObservableCollection<ISessionMember>();
        private ObservableCollection<ISessionMember> currentShouters = new ObservableCollection<ISessionMember>();
        private ListenerState shoutBoxListenerState;
        private double volume;
        private bool isShouting;
        private ISessionMember shoutTarget;
        private Visibility progressVisibility = Visibility.Collapsed;
        private string statusLine;
        private bool speakButtonIsEnabled;
        private bool disposed = false;


        /// <summary>
        /// Gets the shoutbox instance
        /// </summary>
        public abstract IShoutBox ShoutBox { get; }

        /// <summary>
        /// Gets the audio-in platform adapter
        /// </summary>
        public abstract IPlatformAdapterAudioIn PlatformAdapter { get; }


        /// <summary>
        /// Gets a collection of visible shoutboxes in the network
        /// </summary>
        public ObservableCollection<ISessionMember> SessionMembers
        {
            get { return this.sessionMembers; }
        }

        /// <summary>
        /// Gets the current shouter collection
        /// </summary>
        public ObservableCollection<ISessionMember> CurrentShouters
        {
            get { return this.currentShouters; }
        }

        /// <summary>
        /// Gets the shoutbox listener state
        /// </summary>
        public ListenerState ShoutBoxListenerState
        {
            get { return this.shoutBoxListenerState; }

            protected set
            {
                if (this.shoutBoxListenerState != value)
                {
                    this.shoutBoxListenerState = value;

                    OnPropertyChanged("ShoutBoxListenerState");
                }
            }
        }

        /// <summary>
        /// Gets or sets the remote volume level
        /// </summary>
        public double Volume
        {
            get { return this.volume; }
            
            set
            {
                if (this.volume != value)
                {
                    this.volume = value;

                    OnPropertyChanged("Volume");
                }
            }
        }

        /// <summary>
        /// Gets or sets the indication that the local user is shouting
        /// </summary>
        public virtual bool IsShouting
        {
            get { return this.isShouting; }

            set
            {
                if (this.isShouting != value)
                {
                    this.isShouting = value;

                    if (this.IsShouting)
                    {
                        if (this.shoutTarget != null)
                        {
                            this.ShoutBox.StartShout(this.shoutTarget);
                            this.PlatformAdapter.StartRecording();
                        }
                    }
                    else
                    {
                        this.PlatformAdapter.StopRecording();

                        if (this.shoutTarget != null)
                            this.ShoutBox.EndOfShout(this.shoutTarget);
                    }

                    OnPropertyChanged("IsShouting");
                    OnPropertyChanged("IsNotShouting");
                }
            }
        }

        /// <summary>
        /// Gets or sets the indication that the local user is not shouting
        /// (Convenience property to avoid bool converter)
        /// </summary>
        public bool IsNotShouting
        {
            get { return !this.isShouting; }
        }

        /// <summary>
        /// Gets or sets the target which the local user is shouting at
        /// </summary>
        public ISessionMember ShoutTarget
        {
            get { return this.shoutTarget; }

            set
            {
                if (this.shoutTarget != value)
                {
                    this.shoutTarget = value;

                    OnPropertyChanged("ShoutTarget");
                }
            }
        }

        /// <summary>
        /// Gets or sets the visibility of the progress indicator
        /// </summary>
        public Visibility ProgressVisibility
        {
            get { return this.progressVisibility; }

            protected set
            {
                if (this.progressVisibility != value)
                {
                    this.progressVisibility = value;

                    OnPropertyChanged("ProgressVisibility");
                }
            }
        }

        /// <summary>
        /// Gets the shoutbox version information
        /// </summary>
        public virtual string VersionInfo
        {
            get
            {
                Assembly assembly = Assembly.GetExecutingAssembly();
                
                return assembly.FullName;
            }
        }

        /// <summary>
        /// Gets the status line content
        /// </summary>
        public string StatusLine
        {
            get { return this.statusLine; }

            protected set
            {
                this.statusLine = value;

                OnPropertyChanged("StatusLine");
            }
        }

        /// <summary>
        /// Gets the enabling of the speak button
        /// </summary>
        public bool SpeakButtonIsEnabled
        {
            get { return this.speakButtonIsEnabled; }

            private set
            {
                if (this.speakButtonIsEnabled != value)
                {
                    this.speakButtonIsEnabled = value;

                    OnPropertyChanged("SpeakButtonIsEnabled");
                }
            }
        }


        /// <summary>
        /// Constructor
        /// </summary>
        public ViewModel()
        {
#if WPF
            this.uiDispatcher = Dispatcher.CurrentDispatcher;
#else
            this.uiDispatcher = Deployment.Current.Dispatcher;
#endif
            // progress indicator starts with creation of view model 
            this.ProgressVisibility = Visibility.Visible;

            this.sessionMembers.CollectionChanged += new NotifyCollectionChangedEventHandler(ListenerStateHandler);
            this.currentShouters.CollectionChanged += new NotifyCollectionChangedEventHandler(ListenerStateHandler);
            this.PropertyChanged += new PropertyChangedEventHandler(ViewModel_PropertyChanged);
        }


        /// <summary>
        /// Release all resources
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }


        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    this.ShoutBox.Dispose();
                    this.PlatformAdapter.Dispose();
                }
            }
            disposed = true;
        }


        protected virtual void OnPropertyChanged(String info)
        {
            if (this.PropertyChanged != null)
                this.PropertyChanged(this, new PropertyChangedEventArgs(info));
        }

        protected void SessionMembers_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            // make sure that view is notified on its UI thread
            this.uiDispatcher.BeginInvoke(new Action(delegate()
            {
                if (e.Action == NotifyCollectionChangedAction.Add)
                {
                    // progress indicator ends at first shoutbox detection  
                    this.ProgressVisibility = Visibility.Collapsed;

                    foreach (ISessionMember shoutBox in e.NewItems)
                    {
                        this.sessionMembers.Add(shoutBox);

                        if (shoutBox.IsSender)
                            this.currentShouters.Add(shoutBox);

                        shoutBox.IsSenderChanged += new EventHandler(shoutBox_IsSenderChanged);

                        // in case that there are still no shoutboxes found ... 
                        ShowShoutersInStatusBar();
                    }
                }
                else if (e.Action == NotifyCollectionChangedAction.Remove)
                {
                    foreach (ISessionMember shoutBox in e.OldItems)
                    {
                        this.sessionMembers.Remove(shoutBox);
                        this.currentShouters.Remove(shoutBox); // does no harm when not part of collection
                    }

                    if (this.sessionMembers.Count == 0)
                    {
                        this.statusLine = Strings.NoShoutBoxesFound;
                        OnPropertyChanged("StatusLine");
                    }
                }
                else if (e.Action == NotifyCollectionChangedAction.Reset)
                {
                    this.sessionMembers.Clear();
                    this.currentShouters.Clear();

                    this.statusLine = Strings.NoShoutBoxesFound;
                    OnPropertyChanged("StatusLine");
                }

                if (this.sessionMembers.Count == 0)
                {
                    this.ShoutBoxListenerState = ListenerState.Idle;
                }
                else if (this.sessionMembers.Count == 1)
                {
                    this.ShoutBoxListenerState = ListenerState.Waiting;
                }
            }), null);
        }

        protected void platformAdapter_AudioDataAvailable(object sender, RealTimeDataEventArgs e)
        {
            if ((this.shoutTarget != null) && (this.isShouting))
                // make sure that no data samples are send after EndOfShout (==> IsSender malfunction!)
                this.ShoutBox.StreamShout(this.ShoutTarget,e.Buffer, e.RealTimeDataFormat);
        }

        protected void shoutBox_IsSenderChanged(object sender, EventArgs e)
        {
            ISessionMember sessionMember = sender as ISessionMember;
            if (sessionMember != null)
            {
                bool isSender = sessionMember.IsSender;

                this.uiDispatcher.BeginInvoke(new Action(delegate()
                {
                    if (isSender)
                        this.currentShouters.Add(sessionMember);
                    else
                        this.currentShouters.Remove(sessionMember);

                    ShowShoutersInStatusBar();
                }), null);
            }
        }


        private void ListenerStateHandler(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (this.sessionMembers.Count == 0)
                this.ShoutBoxListenerState = ListenerState.Idle;
            else if (this.currentShouters.Count == 0)
                this.ShoutBoxListenerState = ListenerState.Waiting;
            else
                this.ShoutBoxListenerState = ListenerState.Active;
        }

        private void ShowShoutersInStatusBar()
        {
            // determine collection of session member names 
            var shouterNames = from s in this.currentShouters select s.Name;
            this.statusLine = string.Join(", ", shouterNames);

            OnPropertyChanged("StatusLine");
        }

        private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            // determine enabling condition of speak button
            if (this.isShouting)
            {
                // it must always be possible to terminate an ongoing shout
                this.SpeakButtonIsEnabled = true;
            }
            else if (e.PropertyName.Equals("ShoutBoxListenerState"))
            {
                if (this.shoutBoxListenerState == ListenerState.Waiting)
                {
                    // shouting is enabled when targets are available and no incoming shouts are present
                    this.SpeakButtonIsEnabled = true;
                }
                else
                    this.SpeakButtonIsEnabled = false;
            }
        }
    }
}
